/*
 *  Master.java detects the presence of a dock, then monitors accelerometers, asks if a crash has happened and then sends messages to stored contacts if one has 
 *  Copyright (C) 2010  Andrew Fasano
 *
 *   This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 */

package org.tds.master;


/*
 * @Author		Andrew Fasano
 */

import java.io.IOException;
import java.util.List;
import java.util.Locale;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.KeyguardManager;
import android.app.Service;
import android.app.KeyguardManager.KeyguardLock;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.DialogInterface.OnClickListener;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.location.Address;
import android.location.Criteria;
import android.location.Geocoder;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.telephony.SmsManager;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.WindowManager;
import android.widget.TextView;
import android.widget.Toast;



//This will be running constantly after the app is setup
public class Master extends Activity implements SensorEventListener{
	final int dockMagValue = 400; //This number has been between 100 and 1000 with a testing magnet
	//This will need to be configured with the dock
	final String TAG = "TDS-Master";
	SensorManager sm = null;
	Sensor magSensor = null;
	Boolean hasMag = false;
	Boolean inDock = false;
	MediaPlayer mp = null;
	int lastState = -1;

	//indock vars
	final int maxMag = 11;
	SensorManager s;
	TextView output;
	int oldRingType;
	AudioManager soundManager;
	AlertDialog d;  
	boolean unlocked = false;
	KeyguardManager kg;

	//Event Detection vars
	LocationManager lm;
	locList list;
	boolean hasSpeed = false;
	double critSpeed = 2;
	TextView timeLeft = null;
	int countdownStart = 60;
	int countdownTime = countdownStart;
	private RefreshHandler updater = new RefreshHandler();	


	//Sned Messages vars
	gpsListener listener = new gpsListener();
	GMailSender sender;
	Geocoder geocoder;


	public void onCreate(Bundle icicle) {
		super.onCreate(icicle);
		Log.i(TAG,"WaitingForDock was created");
		setContentView(R.layout.waiting_main);
		registerReceiver(batRC, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));  
		sm = (SensorManager) getBaseContext().getSystemService(Service.SENSOR_SERVICE); 
		magSensor = sm.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD); 
		mp = MediaPlayer.create(this, R.raw.beep);		
    	geocoder = new Geocoder(this, Locale.getDefault());
		sender = new GMailSender("tds.emails","thepassword");				
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		menu.add(0, 1, 0, "Edit info");
		return true;
	}
	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		switch (item.getItemId()) {
        case 1:
        	Intent i = new Intent(getApplicationContext(), Setup.class);
        	i.putExtra("update", true);
        	startActivity(i);
        	return true;
		}
		return false;
	}

	public BroadcastReceiver batRC = new BroadcastReceiver() {
		@Override
		public void onReceive(Context context, Intent intent) {
			int state = intent.getIntExtra("plugged", -1);

			if (inDock && state == 0)
				leftDock();

			//Toast.makeText(getApplicationContext(), "Battery Changed: "+state, Toast.LENGTH_SHORT).show();
			if (state != lastState && lastState+state != 3) { //For some reason, this function is called more than once when the battery is charging	
				//It also gets called when plugged into usb twice. I'm not positive this second condition is necessary
				lastState = state;
				if (state == 1 || state == 2) {
					dockCheck(false);
				}
			}
		}
	};

	public void dockCheck(boolean hasChecked) {
		//Toast.makeText(getApplicationContext(), "dockCheck", Toast.LENGTH_SHORT).show();

		if (!hasChecked) { //It only needs to check once
			sm.registerListener(this, magSensor, SensorManager.SENSOR_DELAY_NORMAL);
		}else{
			sm.unregisterListener(this, magSensor);
		}

		if (hasMag) {
			docked();
		}  	
	}

	public void docked() { //Called when phone is put in dock
		if (inDock)
			return;

		//Toast.makeText(getApplicationContext(), "Now in dock.", Toast.LENGTH_SHORT).show();
		getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
		getWindow().addFlags(WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW);

		mp.start();
		inDock = true;
		Log.i(TAG,"Dock detected, starting inDock section");



		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		///// IN DOCK ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		//		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


		setContentView(R.layout.dock_main);
		getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
				| WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
				| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
				| WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);

		//Save old ring volume and set to silent for now
		soundManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
		oldRingType = soundManager.getRingerMode();
		soundManager.setRingerMode(AudioManager.RINGER_MODE_SILENT);
		Log.i(TAG,"Ringer mode changed from "+oldRingType+" to silent");


		takeFocus();

	}
	public void takeFocus() {
		Log.i(TAG,"InDock Taking focus");

		if (s == null)
			s = (SensorManager) getApplicationContext().getSystemService(Context.SENSOR_SERVICE);
		s.registerListener(sel, s.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_NORMAL); 

		if (kg == null)
			kg = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
		if (kg.inKeyguardRestrictedInputMode()) {//If keyguard is up
			unlocked = true;
			KeyguardLock kgLock = kg.newKeyguardLock("TDS");
			kgLock.disableKeyguard();
		}

		View v = View.inflate(getBaseContext(), R.layout.dock_alert, null);
		output = (TextView) v.findViewById(R.id.output);

		AlertDialog.Builder b = new AlertDialog.Builder(getBaseContext());
		b.setCancelable(true);
		b.setTitle("Dock detected!");
		b.setView(v);
		b.setIcon(android.R.drawable.ic_dialog_alert);
		b.setPositiveButton("Abort", l);

		d = b.create();
		d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
		d.show();		
	}

	private DialogInterface.OnClickListener l = new DialogInterface.OnClickListener() {
		@Override
		public void onClick(DialogInterface dialog, int which) {
			Toast.makeText(getBaseContext(), "Canceled!", Toast.LENGTH_LONG);	
			Log.i(TAG,"Canceled");
			leftDock();
		}
	};   

	public void leftDock() {
		Log.i(TAG,"Done with InDock code");
		getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
		s.unregisterListener(sel);
		inDock = false;
		d.dismiss();

		//Revert old ring volume
		soundManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
		soundManager.setRingerMode(oldRingType);
		Log.i(TAG,"Ringer mode changed back to "+oldRingType);

		//Put keyguard back
		if (unlocked) {
			KeyguardManager kgRel = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
			kgRel.newKeyguardLock("TDS").reenableKeyguard();
		}

		setContentView(R.layout.waiting_main);

	}

	private SensorEventListener sel = new SensorEventListener() {		
		public void onAccuracyChanged(Sensor sensor, int accuracy) {
			///Log.d(TAG,"Accuracy changed");
		}

		public void onSensorChanged(SensorEvent event) {
			float[] vals = event.values;
			float left = vals[0];
			float down = vals[1];
			float in = vals[2];
			//Log.d(TAG,"Sensor changed- left: "+left+" down: "+down+" in: "+in);
			checkData(left,down,in);
		}    	
	};

	public float findMag (float...dirs) {
		float rtn = 0;
		for (int i=0; i< dirs.length; i++) {
			rtn += Math.pow(dirs[i],2);
		}
		rtn = (float) Math.sqrt(rtn);
		return rtn;
	}

	public void checkData(float l, float d, float i) {
		float mag = findMag(l,d,i);
		output.setText("Magnitude: "+mag);

		if (mag > maxMag) {
			s.unregisterListener(sel);
			unregisterReceiver(batRC);

			Log.i(TAG,"Event detected, going to EventDetected section");
			eventDetected();

		}
	}
	/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	////// END IN DOCK //////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

	/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	////// START EVENT DETECTED //////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

	public void eventDetected() {
		d.dismiss();
		lm = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
		Criteria crit = new Criteria();
		crit.setSpeedRequired(true);
		list = new locList();
		lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, list );
		takeFocusED();
	}

	public void takeFocusED() {
		Log.i(TAG,"EventDetected section is taking focus");

		View v = View.inflate(getBaseContext(), R.layout.dock_event, null);
		doCountdown();

		AlertDialog.Builder b = new AlertDialog.Builder(getBaseContext());
		b.setCancelable(true);
		b.setTitle("Crash detected!");
		b.setView(v);
		b.setIcon(android.R.drawable.ic_dialog_alert);
		b.setPositiveButton("Yes", crashed);
		b.setNegativeButton("No, abort", safe);

		d = b.create();
		d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
		d.show();		

	}

	private DialogInterface.OnClickListener crashed = new DialogInterface.OnClickListener() {
		@Override
		public void onClick(DialogInterface dialog, int which) {	
			sendMessages(true);
		}		
	};   

	private DialogInterface.OnClickListener safe = new DialogInterface.OnClickListener() {

		@Override
		public void onClick(DialogInterface dialog, int which) {
			canceled();		
			
		}
	};

	private class locList implements LocationListener {
		@Override
		public void onLocationChanged(Location loc) {
			if (loc.hasSpeed()) {
				hasSpeed = true;
				Log.i(TAG,"Speed is "+loc.getSpeed());
				if (loc.getSpeed() > critSpeed) {
					canceled();
					lm.removeUpdates(this);
				}
			}else{
				hasSpeed = false;
				Log.w(TAG,"No speed data");
			}
		}
		@Override
		public void onProviderDisabled(String provider) {}
		@Override
		public void onProviderEnabled(String provider) {}
		@Override
		public void onStatusChanged(String provider, int status, Bundle extras) {}
	}

	private void doCountdown() { 		
		countdownTime--;

		Log.i(TAG,"Countdown time left: "+countdownTime);

		if (countdownTime == Math.round(countdownStart/2) && hasSpeed) {
			alertUser();
		}

		if(countdownTime > 0){
			updater.sleep(1000);
		}else{
			sendMessages(false, hasSpeed);
		}

	}

	class RefreshHandler extends Handler {
		public void sleep(long delayMillis) {
			this.removeMessages(0);
			sendMessageDelayed(obtainMessage(0), delayMillis);
		}

		@Override
		public void handleMessage(Message msg) {
			Master.this.doCountdown();
		}
	};

	public void alertUser() {
		Log.i(TAG,"Tell the user that they need to interact with the app");

		mp.start();
	}

	public void endED() {
		lm.removeUpdates(list);
		updater.removeMessages(0);
		countdownTime = countdownStart;
		d.dismiss();
	}


	public void canceled() {
		Log.i(TAG,"Event Detected was a false alarm");
		endED();
		registerReceiver(batRC, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
		takeFocus();

	}



	/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	////// END EVENT DETECTED //////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

	/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	////// START SEND MESSAGES //////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

	public void sendMessages(boolean... confirmed) { //Confirmed, hasSpeed
		Log.i(TAG,"Event Detected was legit, send messages");
		endED();
		
		View v = View.inflate(getBaseContext(), R.layout.sending_main, null);
		AlertDialog.Builder b = new AlertDialog.Builder(getBaseContext());
		b.setCancelable(true);
		b.setTitle("Sending alerts...");
		b.setView(v);
		b.setIcon(android.R.drawable.ic_dialog_alert);

		d = b.create();
		d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
		d.show();
		
		lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, listener);
	}	



	private class gpsListener implements LocationListener {
		public void onLocationChanged(Location gpsLoc) {
			String add = null;
			double lat = 0;
			double lon = 0;

			try {
				lat = gpsLoc.getLatitude();
				lon = gpsLoc.getLongitude();
				lm.removeUpdates(listener);
				List<Address> addresses = geocoder.getFromLocation(lat,lon,1);

				add = "";
				for(int i = 0; true; i++) {
					if (addresses.get(0).getAddressLine(i) != null) {
						add+=addresses.get(0).getAddressLine(i)+", ";
					}else{
						break;
					}
				}
			} catch (IOException e1) {}

			//Text messages
			SmsManager sm = SmsManager.getDefault();

			String sms= "Alert from "+add;
			String contactNumber;

			if ((contactNumber = getIntent().getExtras().getString("contact1phone")) != null) {
				sm.sendMultipartTextMessage(contactNumber, null, sm.divideMessage(sms), null, null);
				Log.i(TAG,"(1) Just sent "+sms+" to "+contactNumber);
			}

			if ((contactNumber = getIntent().getExtras().getString("contact2phone")) != null) {
				sm.sendMultipartTextMessage(contactNumber, null, sm.divideMessage(sms), null, null);
				Log.i(TAG,"(2) Just sent "+sms+" to "+contactNumber);
			}    

			//Emails
			String emailMsg = "An incident was detected by the TDS software. The address of the alert is: " +add+". The exact coordinates of this are "+lat+", "+lon+". Click on the following link to view that location on a map: http://maps.google.com/maps?f=q&source=s_q&hl=en&geocode=&q="+lat+","+lon;
			String emailSubject = "Alert from TDS";
			String contactAddr;
			if ((contactAddr = getIntent().getExtras().getString("contact1email")) != null) {
				sendEmail(emailSubject,emailMsg,contactAddr);
				Log.i(TAG,"(1) Just sent "+emailMsg+" to "+contactAddr);
			}

			if ((contactAddr = getIntent().getExtras().getString("contact2email")) != null) {
				sendEmail(emailSubject,emailMsg,contactAddr);
				Log.i(TAG,"(2) Just sent "+emailMsg+" to "+contactAddr);
			}

			lm.removeUpdates(list);
			
			//Finished sending
			View v = View.inflate(getBaseContext(), R.layout.sending_done, null);

			d.dismiss();
			
			AlertDialog.Builder b = new AlertDialog.Builder(getBaseContext());
			b.setCancelable(true);
			b.setTitle("Alerts Sent!");
			b.setView(v);
			b.setIcon(android.R.drawable.ic_dialog_alert);
			OnClickListener okay = new DialogInterface.OnClickListener() {
				@Override
				public void onClick(DialogInterface dialog, int which) {
					d.dismiss();
					leftDock();
				}
			};
			b.setPositiveButton("Okay", okay );

			d = b.create();
			d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
			d.show();
  

		}

		
		public void onProviderDisabled(String provider) {}

		public void onProviderEnabled(String provider) {}

		public void onStatusChanged(String provider, int status, Bundle extras) {}
	}

	public void sendEmail(String subject, String body, String to) {
		try {
			sender.sendMail(subject, body, "TdsEmails@gmail.com", to);
		} catch (Exception e) {}
	}

	/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	////// END SEND MESSAGES //////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


	//Implementing SensorEventListenere requires the next two functions. And we don't care about sensor accuracy
	@Override
	public void onAccuracyChanged(Sensor arg0, int agr1) {}

	@Override
	public void onSensorChanged(SensorEvent event) {
		//The magnitude of the force is the sqrt of the 3 components squared
		double mag = Math.sqrt(event.values[0]*event.values[0]+event.values[1]*event.values[1]+event.values[2]*event.values[2]);

		Log.i(TAG,"Now charging. Magnetic field strength: "+mag);

		if (mag > dockMagValue)
			hasMag = true;


		dockCheck(true); //After this has a reading, go back to dockCheck
	}

	@Override
	public void onDestroy() {
		Log.i(TAG, "WaitingForDock was destroyed");
		super.onDestroy();
	}


}